package socketmvc.core.processor.bean;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.bytecode.*;
import socketmvc.core.exception.ProcessorMethodParametricAnalysisException;
import socketmvc.core.processor.annotation.SocketParam;
import socketmvc.core.processor.annotation.SocketBody;
import socketmvc.core.processor.annotation.SocketResponse;

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by Mr.xue on 2018/1/15.
 * 存储请求类与函数信息
 */

public class ProcessorMethod {

    /*对应命令码*/
    private String command;

    private String name;

    private Processor<?> processor;
    /*函数反射信息*/
    private Method method;
    /*入参信息*/
    private List<ProcessorMethodParam> params;
    /*返回值信息*/
    private ProcessorMethodReturn returnObj;

    public ProcessorMethod(String cmd,Processor<?> processor, Method method) {
        this(cmd,processor,method,null,null);
        this.params = getMethodParams(this);
        this.returnObj = getMethodReturn(this);
    }

    public ProcessorMethod(String cmd,Processor<?> processor, Method method, List<ProcessorMethodParam> params,ProcessorMethodReturn returnObj) {
        this.command = cmd;
        this.processor = processor;
        this.method = method;
        this.params = params;
        this.returnObj = returnObj;
        this.name = this.processor.getName() + "#" + this.method.getName();
    }


    /**
     * method中的每个参数都会调用该方法
     * 用来创建处理参数对象
     * @param paramName 参数名
     * @param paramClass 参数class
     * @param paramType 参数type
     * @return 参数对象
     */
    protected ProcessorMethodParam createMethodParam(String paramName,Class<?> paramClass,Type paramType){
        return new ProcessorMethodParam(paramName,paramClass,paramType);
    }

    /**
     * 创建返回值处理对象
     * @param returnName 返回值名称
     * @param returnClass 返回值class
     * @param returnType 返回值type
     * @return 返回值对象
     */
    protected ProcessorMethodReturn createMethodReturn(String returnName,Class<?> returnClass,Type returnType){
        return new ProcessorMethodReturn(returnName,returnClass,returnType);
    }
    /**
     *
     * 获取方法参数列表
     * @param pMethod me
     * @return 封装好的参数列表
     * @throws ProcessorMethodParametricAnalysisException 参数分析异常
     */
    protected static List<ProcessorMethodParam> getMethodParams(ProcessorMethod pMethod) throws ProcessorMethodParametricAnalysisException {
        Method method = pMethod.getMethod();
        Class<?> clazz = method.getDeclaringClass();
        Class<?>[] oldParameterTypes = method.getParameterTypes();
        Parameter[] parameters = method.getParameters();
        List<ProcessorMethodParam> processorMethodParams = new ArrayList<>(parameters.length);

        if (parameters.length > 0){
            // 采用java字节码分析获取参数列表
            ClassPool pool = ClassPool.getDefault();
            try {
                CtClass clz = pool.get(clazz.getName());
                CtClass[] params = new CtClass[oldParameterTypes.length];
                for (int i = 0; i < oldParameterTypes.length; i++) {
                    params[i] = pool.getCtClass(oldParameterTypes[i].getName());
                }
                CtMethod cm = clz.getDeclaredMethod(method.getName(), params);
                MethodInfo methodInfo = cm.getMethodInfo();
                CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
                LocalVariableAttribute paramAttr = (LocalVariableAttribute) codeAttribute
                        .getAttribute(LocalVariableAttribute.tag);
                int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;

                if (pos == 0){ // 静态方法不需要实例调用
                    Processor<?> _p = pMethod.getProcessor();
                    Processor<?> _p1 = new Processor<>(_p.getName(), _p.getClazz(), null);
                    pMethod.setProcessor(_p1);
                }
                // 从局部变量表中分析方法行参名称
                ArrayList<String> paramsName = new ArrayList<>();
                int localVarTableLen = paramAttr.tableLength();
                for (int i = 0; i < localVarTableLen; i++) {
                    String name = paramAttr.variableName(i);
                    if (!name.equals("this")){
                        continue;
                    }
                    paramsName.add(name);
                    for (int j = 0;j < parameters.length; j++) {
                        name = paramAttr.variableName(i + j + 1);
                        paramsName.add(name);
                    }
                    break;
                }

                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    SocketParam socketParamAnnotation = parameter.getAnnotation(SocketParam.class);
                    SocketBody socketBodyAnnotation = parameter.getAnnotation(SocketBody.class);
                    String paramName = null;
                    if (socketBodyAnnotation == null){
                        if (socketParamAnnotation != null){
                            if (!socketParamAnnotation.root()){
                                paramName = socketParamAnnotation.value();
                            }
                        }else {
                            if (paramsName.size() > 0){
                                paramName = paramsName.get(i + pos);
                            }else {
                                paramName = parameter.getName();
                            }
                        }
                    }
                    Type paramType = parameter.getParameterizedType();
                    Class<?> paramClazz = parameter.getType();
                    processorMethodParams.add(pMethod.createMethodParam(paramName,paramClazz,paramType));
                }
            }catch (Exception e){
                throw new ProcessorMethodParametricAnalysisException("processor 参数分析阶段发生异常 ===>",e);
            }

        }
        return processorMethodParams;
    }

    protected static ProcessorMethodReturn getMethodReturn(ProcessorMethod pMethod){
        Method method = pMethod.getMethod();
        AnnotatedType annotatedReturnType = method.getAnnotatedReturnType();
        SocketResponse responseAnnotation = annotatedReturnType.getAnnotation(SocketResponse.class);
        String returnName = null;
        if (responseAnnotation!=null){
            returnName = responseAnnotation.returnName();
        }
       return  pMethod.createMethodReturn(returnName,method.getReturnType(),annotatedReturnType.getType());
    }

    public String getCommand() {
        return command;
    }

    public void setCommand(String command) {
        this.command = command;
    }

    public Processor<?> getProcessor() {
        return processor;
    }

    public void setProcessor(Processor<?> processor) {
        this.processor = processor;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public List<ProcessorMethodParam> getParams() {
        return params;
    }

    public void setParams(List<ProcessorMethodParam> params) {
        this.params = params;
    }

    public ProcessorMethodReturn getReturn() {
        return returnObj;
    }

    public void setReturn(ProcessorMethodReturn returnObj) {
        this.returnObj = returnObj;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Object invoke(Object... args) throws InvocationTargetException, IllegalAccessException {
       return this.method.invoke(this.processor.getInstance(),args);
    }
}
